在home資料夾下新增「movie_detail_page.dart」
首先先設定建構子,傳入要顯示的電影資訊,並初始化YoutubeRepository和YoutubeBloc(要記得引入youtube.dart)。
class MovieDetailPage extends StatefulWidget {
  final posterPath;
  final overview;
  final releaseDate;
  final title;
  final voteAverage;
  final movieId;
  MovieDetailPage(
      {Key key,
      this.posterPath,
      this.overview,
      this.releaseDate,
      this.title,
      this.voteAverage,
      this.movieId})
      : super(key: key);
  @override
  _MovieDetailPageState createState() => _MovieDetailPageState();
}
class _MovieDetailPageState extends State<MovieDetailPage> {
  String get posterPath => widget.posterPath;
  String get overview => widget.overview;
  String get releaseDate => widget.releaseDate;
  String get title => widget.title;
  String get voteAverage => widget.voteAverage.toString();
  String get movieId => widget.movieId.toString();
  
  bool isOverviewSelected = false;
  YoutubeBloc _youtubeBloc;
  YoutubeRepository _youtubeRepository;
  @override
  void initState() {
    _youtubeRepository = YoutubeRepository();
    _youtubeBloc = YoutubeBloc(youtubeRepository: _youtubeRepository);
    _youtubeBloc.dispatch(SearchYoutubeEvent("$title 預告片"));
    super.initState();
  }
}
接下來是UI的程式碼
做到了以下幾個功能:
SliverAppBar讓AppBar在向下滑動的時候會被隱藏起來SliverList包覆起來GestureDetector讓使用者可以縮放大綱文字BlocBuilder監聽YoutubeBloc狀態,顯示搜尋到的預告片  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
          top: false,
          bottom: false,
          child: CustomScrollView(slivers: <Widget>[
            SliverAppBar(
              expandedHeight: 200.0,
              floating: false,
              pinned: false,
              elevation: 0.0,
              flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                "https://image.tmdb.org/t/p/w500${posterPath}",
                fit: BoxFit.cover,
              )),
            ),
            SliverList(
              delegate: SliverChildListDelegate(
                [
                  Container(margin: EdgeInsets.only(top: 5.0)),
                  Text(
                    title,
                    style: TextStyle(
                      fontSize: 25.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  Row(
                    children: <Widget>[
                      Icon(
                        Icons.favorite,
                        color: Colors.red,
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 1.0, right: 1.0),
                      ),
                      Text(
                        voteAverage,
                        style: TextStyle(
                          fontSize: 18.0,
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 10.0, right: 50.0),
                      ),
                      Text(
                        "上映日期:${releaseDate}",
                        style: TextStyle(
                          fontSize: 16.0,
                        ),
                      ),
                    ],
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  isOverviewSelected
                      ? GestureDetector(
                          onTap: () => setState(() {
                            isOverviewSelected = !isOverviewSelected;
                          }),
                          child: Text(overview),
                        )
                      : GestureDetector(
                          onTap: () => setState(() {
                            isOverviewSelected = !isOverviewSelected;
                          }),
                          child: Column(
                            children: <Widget>[
                              ConstrainedBox(
                                constraints: BoxConstraints(maxHeight: 200),
                                child: Text(
                                  overview,
                                  softWrap: true,
                                  overflow: TextOverflow.visible,
                                  maxLines: 2,
                                ),
                              ),
                              Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  Icon(Icons.arrow_drop_down),
                                  Text('閱讀全文'),
                                ],
                              )
                            ],
                          ),
                        ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  Text(
                    "Trailer",
                    style: TextStyle(
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  BlocBuilder(
                    bloc: _youtubeBloc,
                    builder: (context, state) {
                      if (state is YoutubeSuccessState) {
                        return trailerWidget(state.ytResult);
                      }
                      return Center(child: CircularProgressIndicator(),);
                    },
                  ),
                ],
              ),
            )
          ])),
    );
  }
}
稍微介紹一下SliverList和SliverAppBar
A sliver is a portion of a scrollable area. You can use slivers to achieve custom scrolling effects.
以上是Flutter官方對Sliver的解釋,非常清楚Sliver代表可以「滑動」的區域。
平常使用的ListView或是GridView也是由Sliver所實做出來的。
使用Sliver的好處是可以創造出更豐富的滑動效果,或是我們這邊要的——讓AppBar能自動隱藏。
想知道如何使用或是更多實際的效果可以看這篇medium
在home資料夾下新增「trailer_widget.dart」
創造一個能把YoutubeBloc回傳的影片資訊顯示出來的GridView。
程式碼:
import 'package:flutter/material.dart';
import 'package:youtube_api/youtube_api.dart';
import '../youtube/youtube_player_dialog.dart';
Widget trailerWidget(List<YT_API> videos) {
  if (videos.length > 0) {
    return GridView.builder(
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      itemCount: videos.length > 4 ? 4 : videos.length,
      gridDelegate:
      SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
      itemBuilder: (context, index) {
        return Column(
          children: <Widget>[
            GestureDetector(
              child: Image.network(videos[index].thumbnail['default']['url']),
              onTap: () => showDialog(
                  context: context,
                  builder: (context) => YoutubePlayerDialog(
                    videoUrl: videos[index].id,
                  )),
            ),
            ConstrainedBox(
              constraints: BoxConstraints(maxWidth: 130),
              child: Text(
                videos[index].title,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            )
          ],
        );
      },
    );
  } else {
    return Center(
      child: Text(
        '找尋不到影片...',
        style: TextStyle(fontSize: 30, fontWeight: FontWeight.w900),
      ),
    );
  }
}
shrinkWrap一定要設定為true,不然GridView會被無限拉大。itemCount可以依照你的需求調整要顯示的影片數量。
在youtube資料夾新增「youtube_player_dialog.dart」,當使用者點擊影片縮圖就彈出一個Youtube的影片撥放器播放預告片。
程式碼很簡單,使用上次載好的Youtube_Player套件給它影片的id就可以了。
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import 'package:flutter/material.dart';
class YoutubePlayerDialog extends StatefulWidget {
  final String _videoUrl;
  YoutubePlayerDialog({Key key, String videoUrl})
      : _videoUrl = videoUrl,
        super(key: key);
  @override
  _YoutubePlayerDialogState createState() => _YoutubePlayerDialogState();
}
class _YoutubePlayerDialogState extends State<YoutubePlayerDialog> {
  String get videoUrl => widget._videoUrl;
  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: YoutubePlayer(
        context: context,
        videoId: videoUrl,
        flags: YoutubePlayerFlags(
          autoPlay: true,
          showVideoProgressIndicator: true,
        ),
      ),
    );
  }
}
在Android跳轉頁面是用Intent而在Flutter是使用Navigator.push。
開啟show_movie_widget.dart增加點擊跳轉頁面的程式碼。
GestureDetector(
        onTap: () =>Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => MovieDetailPage(
                            posterPath: movieList
                                .results[currentPage.round()].backdropPath,
                            overview:
                                movieList.results[currentPage.round()].overview,
                            title: movieList.results[currentPage.round()].title,
                            releaseDate: movieList
                                .results[currentPage.round()].releaseDate,
                            voteAverage: movieList
                                .results[currentPage.round()].voteAverage,
                            movieId: movieList.results[currentPage.round()].id,
                          ))),
        child: Padding(padding: EdgeInsets.only(top: 10.0), 
		child: Text(
                '點擊看更多細節與預告片',
                style: TextStyle(
                    color: Colors.amberAccent,
                    fontSize: 14.0,
                    fontWeight: FontWeight.bold),
        ),)
),

今天把電影細節頁面的排版完成、也把播放預告片的功能做完了。比較可惜的是影片只有單純地用關鍵字在Youtube搜尋,可能會找到同名的影片甚至完全無關的,這部分可能就要額外處理了。
我想明天就多增加一個留言功能讓大家能夠討論電影心得,為了能快速開發就使用Firebase提供的Cloud Firestore作為我們的資料庫吧。
完整程式碼在這裡-> FlutTube Github